package com.code.bz.aspects;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.code.bz.annotations.OperateLogAnno;
import com.code.bz.consts.HeaderConstants;
import com.code.bz.enums.ResultCode;
import com.code.bz.exceptions.BusinessException;
import com.code.bz.handlers.GlobalExceptionHandler;
import com.code.bz.models.po.OperateLog;
import com.code.bz.services.OperateLogService;
import com.code.bz.services.RobotTaskCheckService;
import com.code.bz.utils.IpUtil;
import com.code.bz.utils.JsonUtil;
import com.code.models.robot.RobotTaskCheck;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.List;

/**
 * 请求参数、响应体统一日志打印
 *
 * @author xiaoyaowang
 */
@Slf4j
@Aspect
public class RestControllerAspect {

    @Resource
    private OperateLogService operateLogService;

    @Resource
    private RobotTaskCheckService robotTaskCheckService;

    /**
     * 环绕通知
     *
     * @param joinPoint 连接点
     * @return 切入点返回值
     * @throws Throwable 异常信息
     */
    @Around("@within(org.springframework.web.bind.annotation.RestController) || @annotation(org.springframework.web.bind.annotation.RestController)")
    public Object apiLog(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 为true需要记录日志，为false就是不记录日志
        boolean logFlag = this.needToLog(method);
        // 当不记录日志的情况下，执行目标方法
        if (!logFlag) {
            long begin = System.currentTimeMillis();
            Object result = joinPoint.proceed();
            log.info("处理完成总的消耗时间：{}毫秒", System.currentTimeMillis() - begin);
            return result;
        }

        // 通过线程安全模式获取上下文请求
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        // 获取调用来源，比如web网站、微信、ios、安卓
        String callSource = request.getHeader(HeaderConstants.CALL_SOURCE);
        // 获取请求中的ip地址
        String ip = IpUtil.getRealIp(request);
        // 获取目标方法名称
        String methodName = this.getMethodName(joinPoint);
        // 获取目标方法参数信息并且移除敏感内容
        String params = this.getParamsJson(joinPoint);
        // 获取用户的引擎
        String userAgent = request.getHeader("order-agent");
        if (StringUtils.isBlank(userAgent)) {
            userAgent = request.getHeader("user-agent");
        }
        if (method.isAnnotationPresent(OperateLogAnno.class)) {
            OperateLogAnno operateLogAnno = method.getAnnotation(OperateLogAnno.class);
            OperateLog operateLog = new OperateLog();
            operateLog.setClientIp(ip);
            String accountName = request.getHeader(HeaderConstants.ACCOUNT_NAME);
            if (StringUtils.isBlank(accountName)) {
                throw new BusinessException(ResultCode.ACCOUNT_NAME_ERROR);
            }
            operateLog.setAccountName(accountName);
            operateLog.setPageName(operateLogAnno.pageName());
            operateLog.setOperateItem(operateLogAnno.operateItem());
            // 更新审核状态日志记录
            if (methodName.contains("updateRobotTaskState")) {
                JSONObject paramJson = JSON.parseObject(params);
                if (null != paramJson) {
                    if (paramJson.containsKey("id")) {
                        // 获取审核
                        RobotTaskCheck robotTaskCheck = robotTaskCheckService.getRobotTaskCheckById(paramJson.getInteger("id"));
                        if (null != robotTaskCheck) {
                            operateLog.setTelegramName(robotTaskCheck.getUserName());
                            operateLog.setBzAccount(robotTaskCheck.getBzAccount());
                        }
                    }
                    if (paramJson.containsKey("state")) {
                        Integer state = paramJson.getInteger("state");
                        if (state == 1) {
                            operateLog.setOperateType("审核失败");
                        }
                        if (state == 2) {
                            operateLog.setOperateType("审核成功");
                        }

                    }

                }
            }
            operateLog.setUserAgent(userAgent);
            operateLogService.insertOperateLog(operateLog);

        }

        log.info("Started request method [{}] params [{}] IP [{}] callSource [{}] userAgent [{}]", methodName, params, ip, callSource, userAgent);
        long start = System.currentTimeMillis();
        // 执行目标方法
        Object result = joinPoint.proceed();
        log.info("Ended request method [{}] params[{}] response is [{}] cost [{}] millis ", methodName, params, this.deleteSensitiveContent(result), System.currentTimeMillis() - start);
        return result;
    }

    /**
     * 获取目标方法名称
     *
     * @param joinPoint 连接点
     * @return 目标方法名称
     */
    private String getMethodName(ProceedingJoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().toShortString();
        String shortMethodNameSuffix = "(..)";
        if (methodName.endsWith(shortMethodNameSuffix)) {
            methodName = methodName.substring(0, methodName.length() - shortMethodNameSuffix.length());
        }
        return methodName;
    }

    /**
     * 获取目标方法的参数，去除参数中的敏感字段
     *
     * @param joinPoint 连接点
     * @return 目标方法去除敏感字段的参数
     */
    private String getParamsJson(ProceedingJoinPoint joinPoint) {
        // 获取参数数组
        Object[] args = joinPoint.getArgs();
        if (ArrayUtils.isNotEmpty(args)) {
            StringBuilder sb = new StringBuilder();
            // 循环参数数组
            for (Object arg : args) {
                // 移除敏感内容
                String paramStr;
                if (arg instanceof HttpServletResponse) {
                    paramStr = HttpServletResponse.class.getSimpleName();
                } else if (arg instanceof HttpServletRequest) {
                    paramStr = HttpServletRequest.class.getSimpleName();
                } else if (arg instanceof MultipartFile) {
                    long size = ((MultipartFile) arg).getSize();
                    paramStr = MultipartFile.class.getSimpleName() + " size:" + size;
                } else {
                    paramStr = this.deleteSensitiveContent(arg);
                }
                sb.append(paramStr).append(",");
            }
            // 移除最后一个逗号
            return sb.deleteCharAt(sb.length() - 1).toString();
        }
        return null;
    }

    /**
     * 相关的控制器中的方法是否需要记录日志
     *
     * @param method 调用的方法
     * @return 是否打印日志
     */
    private boolean needToLog(Method method) {
        // GET请求和全局异常处理器注解标记的方法不记录日志
        return null == method.getAnnotation(GetMapping.class)
                && !method.getDeclaringClass().equals(GlobalExceptionHandler.class);
    }

    /**
     * 删除参数中的敏感内容
     *
     * @param obj 参数对象
     * @return 去除敏感内容后的参数对象
     */
    private String deleteSensitiveContent(Object obj) {
        JSONObject jsonObject = new JSONObject();
        if (null == obj || obj instanceof Exception) {
            return jsonObject.toJSONString();
        }

        try {
            String param = JSON.toJSONString(obj);
            if (JsonUtil.isJsonObject(param)) {
                jsonObject = JSONObject.parseObject(param);
                // 获取密码等关键字列表，移除这些敏感字段
                List<String> sensitiveFieldList = this.getSensitiveFieldList();
                for (String sensitiveField : sensitiveFieldList) {
                    if (jsonObject.containsKey(sensitiveField)) {
                        // 把这些敏感字段使用*代替
                        jsonObject.put(sensitiveField, "******");
                    }
                }
            }
        } catch (ClassCastException e) {
            return String.valueOf(obj);
        }
        return jsonObject.toJSONString();
    }

    /**
     * 敏感字段列表-密码相关的关键字
     */
    private List<String> getSensitiveFieldList() {
        List<String> sensitiveFieldList = Lists.newArrayList();
        // 这里还可以移出其他的敏感字段
        sensitiveFieldList.add("pwd");
        sensitiveFieldList.add("password");
        return sensitiveFieldList;
    }
}
